import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Scanner;

//
//  @ Project : Project1
//  @ File Name : Game.java
//  @ Date : 1/21/2013
//  @ Author : Graham Tapscott
//
//

/***
 * Game - this class represents the game and contains public methods for creation, loading from a file, 
 * and searching the game world for objects.
 * 
 * @author gtapscott
 *
 */
public class Game extends GameModel{

	
	// instance variables
	private ArrayList<Creature> creatureList;
	private ArrayList<Item> itemList;
	private ArrayList<Party> partyList;
	private HashMap<Integer, Object> gameElements;
	
	/***
	 * Game - Game class no-arg constructor 
	 */
	public Game() {
	}

	/***
	 * loadWorld - this method is responsible for reading the file and dispatching it to the parser
	 * additionally it handles building the indexes connecting the party and characters and items
	 * and characters once the load is complete.
	 * @param filename
	 * @return 
	 * @throws FileNotFoundException 
	 */
	public String loadWorld(String filename) throws FileNotFoundException {
		gameElements = new HashMap<Integer, Object>();
		creatureList = new ArrayList<Creature>();
		itemList = new ArrayList<Item>();
		partyList = new ArrayList<Party>();
		String input[] = null;
		File file;
		
		file = new File (filename);
		Scanner readFile = new Scanner(file);

		while (readFile.hasNextLine()){
			// split each line on a colon
			input = readFile.nextLine().split(":");
			// call the parseLine function for this line
			parseLine(input);
		}

		readFile.close();

		// call the function to build the indexes
		this.buildIndexes();

		return ("World load successful: " + this.toString());
	}

	// NOTE:
	// Below are the methods responsible for searching the game object, I have a problem 
	// with the efficiency of these searches but for purposes of this project a simple linear search
	// is sufficient.
	
	/***
	 * findObjByName - searches the object lists to find all object with a matching name
	 * @param nm a string representing the name of object to search for
	 * @return a string representing the matching object(s)
	 */
	public String findObjByName(String nm) {
		String result = "";
		for(int t=0;t<creatureList.size();t++){
			if(creatureList.get(t).getName().equals(nm)){
				result += creatureList.get(t).toString() + Utils.newLine;	
			}
		}
		for(int t=0;t<itemList.size();t++){
			if(itemList.get(t).getClass()==Artifact.class){
				Artifact af = ((Artifact)itemList.get(t));
				if(af.getName().equals(nm)){
					result += itemList.get(t).toString() + Utils.newLine;		
				}
			}
		}
		
		if(result.length()>0)
			return result;
		return "Not found";
	}
	
	/***
	 * findObjByIndex - searches the object lists to find all object with a matching name
	 * @param idx an integer representing the index of the object to search for
	 * @return a string representing the matching object(s)
	 */
	public String findObjByIndex(int idx) {
		String result = "";
		for(int t=0;t<creatureList.size();t++){
			if(creatureList.get(t).getIndex()==idx){
				result += creatureList.get(t).toString() + Utils.newLine;		
			}
		}
		for(int t=0;t<itemList.size();t++){
			if(itemList.get(t).getIndex()==idx){
				result += itemList.get(t).toString() + Utils.newLine;
			}
		}
		for(int t=0;t<partyList.size();t++){
			if(partyList.get(t).getIndex()==idx){
				result += partyList.get(t).toString() + Utils.newLine;
			}
		}			
		if(result.length()>0)
			return result;
		return "Not found";
	}
	
	/***
	 * findObjByType - searches the object lists to find all object with a matching name
	 * @param type a string representing the type of object to search for
	 * @return a string representing the matching object(s)
	 */
	public String findObjByType(String type) {	
		String result = "";
		for(int t=0;t<creatureList.size();t++){
			if(creatureList.get(t).getType().equals(type)){
				result += creatureList.get(t).toString() + Utils.newLine;		
			}
		}
		for(int t=0;t<itemList.size();t++){
			if(itemList.get(t).getType().equals(type)){
				result += itemList.get(t).toString() + Utils.newLine;
			}
		}
		if(result.length()>0)
			return result;
		return "Not found";
	}

	/***
	 * toString - returns a string representation of the object
	 * @return a string representation of the object
	 */
	public String toString() {
		return ("Game [ Parties:" + partyList.size() + " ] [ Creatures:" + creatureList.size() + " ] [ Items: " + itemList.size() + " ]");
	}

	public String dumpAllObjects() {
		String result = this.toString() + Utils.newLine;
		for(int i=0;i<partyList.size();i++){
			result += partyList.get(i).dumpObject();
		}
		result += "Unassigned items" + Utils.newLine;
		for(int i=0;i<itemList.size();i++){
			if(itemList.get(i).creatureIndex==0)
				result += itemList.get(i).toString() + Utils.newLine;
		}
		result += Utils.newLine + "Unassigned creatures" + Utils.newLine;
		for(int i=0;i<creatureList.size();i++){
			if(creatureList.get(i).partyIndex==0)
				result += creatureList.get(i).toString() + Utils.newLine;
		}		
		return result;
	}
	
	/***
	 * buildIndexes - this method is responsible for building the global game indexes for partyList/itemlist
	 * I call this only after all objects have been read in from the game file. This ensures that all 
	 * index keys will already exist. 	
	 */
	private void buildIndexes(){
		// Build the global game indexes for partyList
		// sort the list first to speed up the search
		Collections.sort(partyList);
		int searchidx;
		for(int t=0;t<creatureList.size();t++){
			searchidx = Collections.binarySearch(partyList,new Party(creatureList.get(t).getPartyIndex()));
			if(searchidx>=0)
				partyList.get(searchidx).addCreature(creatureList.get(t));				
		}

		// Build the global game indexes for itemList
		// sort the list first to speed up the search		
		Collections.sort(creatureList);
		for(int t=0;t<itemList.size();t++){
			searchidx = Collections.binarySearch(creatureList,new Creature(itemList.get(t).getCreatureIndex()));
			if(searchidx>=0)
				creatureList.get(searchidx).addItem(itemList.get(t));
		}		
	}
	
	/***
	 * parseLine - this method is responsible for parsing the line in the file and
	 * adding the respective object to the global list.
	 * @param input split string representing the object to be de-serialized.
	 */
	private void parseLine(String[] input) {
		// local variables
		int idx = 0;
		
		// check for empty string 
		if(input.length<=1)
			return;
		
		// get first character 
		char firstCharacter = input[0].charAt(0);
		
		// create a new object dependent on the first character
		// each class type knows how to de-serialize itself
		switch(firstCharacter){
			case 'p': {
				Party p = new Party();
				p.deSerialize(input);
				gameElements.put(p.getIndex(), p);
				partyList.add(p);				
			}
				break;
			case 'c': {
				// HACK: this is the only way I know can differentiate human from creature at the moment
				// i.e. the number of nodes the split function returned
				if (input.length > 7){
					Creature c = new Creature();
					c.deSerialize(input);
					gameElements.put(c.getIndex(),c);
					creatureList.add(c);					
				}
				else {
					Player p = new Player();
					p.deSerialize(input);
					gameElements.put(p.getIndex(),p);
					creatureList.add(p);
				}
			}
				break;
			case 't': {
				Treasure t = new Treasure();
				t.deSerialize(input);
				gameElements.put(t.getIndex(),t);
				itemList.add(t);				
			}
				break;
			case 'a': {
				Artifact a = new Artifact();
				a.deSerialize(input);
				gameElements.put(a.getIndex(),a);
				itemList.add(a);
			}
				break;
			default:
				break;			
		}
	}	
}

